using System;
using System.Reflection;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Collections.Generic;

namespace Dida.Test
{
	public class ProgressDlg : Gtk.Dialog
	{
		ThreadedTaskQueue taskQueue = new ThreadedTaskQueue();
		Gtk.Label runningTestLabel;
		Gtk.ProgressBar progressBar;
		internal ProgressTreeView progressTreeView;
		Gtk.Button cancelButton;
				
		public ProgressDlg(TestProject testProject)
		{
			SetDefaultSize(500, 400);
			Resizable = true;

			cancelButton = (Gtk.Button)AddButton("Cancel", (int)Gtk.ResponseType.Cancel);

			Gtk.VBox mainVBox = new Gtk.VBox();
			VBox.PackStart(mainVBox, true, true, 0);

			mainVBox.Spacing = 6;
			mainVBox.BorderWidth = 6;

			runningTestLabel = new Gtk.Label();
			runningTestLabel.Xalign = 0;
			mainVBox.PackStart(runningTestLabel, false, true, 0);

			progressBar = new Gtk.ProgressBar();
			progressBar.Fraction = 0;
			mainVBox.PackStart(progressBar, false, true, 0);

			Gtk.ScrolledWindow scrolledWindow = new Gtk.ScrolledWindow();
			scrolledWindow.SetSizeRequest(-1, 200);
			mainVBox.PackStart(scrolledWindow, true, true, 0);

			progressTreeView = new ProgressTreeView(testProject);
			scrolledWindow.AddWithViewport(progressTreeView);
		}

		public void Run(TestProject testProject)
		{
			Thread testsThread = new Thread(TestsThreadFunc);
			testsThread.Start(testProject);

			try
			{
				bool runMsgLoop = true;
				this.Destroyed +=
					delegate(object o, EventArgs e)
					{
						runMsgLoop = false;
					};

				while (runMsgLoop)
				{
					Gtk.Application.RunIteration(false);
					taskQueue.RunNextTask();

					if (!Gtk.Application.EventsPending() && !taskQueue.TasksPending)
					{
						Thread.Sleep(0);
					}
				}
			}
			finally
			{
				testsThread.Abort();
			}
		}

		private void TestsThreadFunc(object testProjectParam)
		{
			TestProject testProject = (TestProject)testProjectParam;
			TestListenersForDlg testListeners = new TestListenersForDlg(this,taskQueue);

			UnitTestingSettings settings = new UnitTestingSettings();
			testProject.RunAll(testListeners, settings);
		}

		internal void SetProgress(float progress)
		{
			progressBar.Fraction = progress;
		}

		internal string CurTestName
		{
			set
			{
				runningTestLabel.Text = string.Format(
					"Running test: {0}", value);
			}
		}

		internal void Finished(int nPassed,int nFailed)
		{
			if (nFailed == 0)
				runningTestLabel.Text = "Finished. All tests passed!";
			else
			{
				runningTestLabel.Text = string.Format(
					"Finished. {0} test(s) passed, {1} test(s) failed", nPassed, nFailed);
			}

			cancelButton.Label = "Close";
		}
	}

	class ProgressTreeView : Gtk.TreeView
	{
		TestProject testProject;
		Gtk.TreeStore model;

		Dictionary<UnitTest, Gtk.TreeIter> testNodes = new Dictionary<UnitTest, Gtk.TreeIter>();
		Dictionary<MethodKey, MethodTreeNode> methodNodesDict = 
			new Dictionary<MethodKey,MethodTreeNode>();

		public ProgressTreeView(TestProject testProject)
		{
			this.testProject = testProject;

			Gtk.CellRendererText nameRender = new Gtk.CellRendererText();
			InsertColumn(0, "Name", nameRender);
			Columns[0].AddAttribute(nameRender, "text", 0);
			Columns[0].Expand = true;

			Gtk.CellRendererProgress progressRenderer = new Gtk.CellRendererProgress();
			InsertColumn(1, "Progress", progressRenderer);
			Columns[1].AddAttribute(progressRenderer, "visible", 1);
			Columns[1].AddAttribute(progressRenderer, "value", 2);
			Columns[1].MinWidth = 100;

			Gtk.CellRendererPixbuf resultRenderer = new Gtk.CellRendererPixbuf();
			InsertColumn(2, "Result", resultRenderer);
			Columns[2].AddAttribute(resultRenderer, "pixbuf", 3);
						
			HeadersVisible = true;

			model = new Gtk.TreeStore(typeof(string), typeof(bool), typeof(float), typeof(Gdk.Pixbuf));
			Model = model;

			foreach (UnitTest test in testProject.Tests)
			{
				Gtk.TreeIter testNode = model.AppendNode();
				model.SetValue(testNode, 0, test.Name);
				model.SetValue(testNode, 1, true);		// progress bar visible

				testNodes.Add(test, testNode);

				foreach (MethodInfo methodInfo in test.TestMethods)
				{
					Gtk.TreeIter nodeIter = model.AppendNode(testNode);
					model.SetValue(nodeIter, 0, methodInfo.Name);
					model.SetValue(nodeIter, 1, false);		// progress bar hidden

					MethodTreeNode methodNode = new MethodTreeNode();
					methodNode.nodeIter = nodeIter;

					methodNodesDict.Add(new MethodKey(test, methodInfo), methodNode);
				}
			}
		}

		private class MethodKey
		{
			UnitTest test;
			MethodInfo method;

			public MethodKey(UnitTest test,MethodInfo method)
			{
				this.test = test;
				this.method = method;
			}

			public override int  GetHashCode()
			{
				return test.GetHashCode() ^ method.GetHashCode();
			}

			public override bool  Equals(object obj)
			{
				MethodKey b = (MethodKey)obj;
				return test == b.test && method == b.method;
			}
		}

		private class MethodTreeNode
		{
			public Gtk.TreeIter nodeIter;
		}

		public Gtk.TreeIter GetTestNode(UnitTest test)
		{
			return testNodes[test];
		}

		public void SetNodeProgress(Gtk.TreeIter node, float progress)
		{
			model.SetValue(node, 2, progress * 100);
		}

		public void AddFailedMethod(UnitTest test,MethodInfo method,string msg)
		{
			MethodTreeNode methodTreeNode = methodNodesDict[new MethodKey(test, method)];
			Gtk.TreeIter failureNode = model.AppendNode(methodTreeNode.nodeIter);

			if(msg != null)
				model.SetValue(failureNode, 0, string.Format("Failed: {0}",msg));
			else
				model.SetValue(failureNode, 0, "Failed: No message");
		}
	}

	class TestListenersForDlg : ITestResultListener
	{
		float curProgress = 0;
		ProgressDlg dlg;
		ThreadedTaskQueue taskQueue;

		int nFailed;
		int nPassed;

		ProgressSegment curTestProgressSeg;
		TestNodeUpdater curNodeUpdater;
		Gtk.TreeIter curTestNode;

		public TestListenersForDlg(ProgressDlg dlg,ThreadedTaskQueue taskQueue)
		{
			this.dlg = dlg;
			this.taskQueue = taskQueue;
		}

		public void Begin(ProgressSegment fullProgressSegment)
		{
			nPassed = 0;
			nFailed = 0;

			fullProgressSegment.Updated += UpdateProgress;
		}

		public void End()
		{
			taskQueue.Add(
				delegate()
				{
					dlg.Finished(nPassed,nFailed);
				});
		}

		public void BeginTest(UnitTest test,ProgressSegment testProgressSegment)
		{
			taskQueue.Add(
				delegate()
				{
					dlg.CurTestName = test.Name;
				});

			curTestProgressSeg = testProgressSegment;
			curTestNode = dlg.progressTreeView.GetTestNode(test);

			curNodeUpdater = new TestNodeUpdater(taskQueue, dlg.progressTreeView,curTestNode);
			testProgressSegment.Updated += curNodeUpdater.OnUpdated;
		}

		public void EndTest(UnitTest test)
		{
			curNodeUpdater.Complete();
		}

		public void BeginRunner(RunnerStateInfo stateInfo)
		{
		}

		public void EndRunner(RunnerStateInfo stateInfo)
		{
		}

		public void BeginIteration(IterationStateInfo stateInfo)
		{
		}

		public void EndIteration(IterationStateInfo stateInfo)
		{	
		}

		public void ReportSuccess(TestMethodResult res)
		{
			nPassed++;
		}

		public void ReportFailure(TestMethodResult res)
		{
			nFailed++;

			taskQueue.Add(
				delegate()
				{
					dlg.progressTreeView.AddFailedMethod(res.UnitTest,res.MethodInfo,res.Message);
				});
		}

		private void UpdateProgress(object o,ProgressSegment.UpdatedEventArgs args)
		{
			float diff = args.GlobalProgress - curProgress;
			if (diff > .001f)
			{
				curProgress = args.GlobalProgress;

				float progressToSet = curProgress;
				taskQueue.Add(
					delegate()
					{
						dlg.SetProgress(progressToSet);
					});
			}
		}

		class TestNodeUpdater
		{
			ThreadedTaskQueue taskQueue;
			ProgressTreeView treeView;
			Gtk.TreeIter nodeIter;
			float prevProgress;

			public TestNodeUpdater(ThreadedTaskQueue taskQueue,
				ProgressTreeView treeView, Gtk.TreeIter nodeIter)
			{
				this.taskQueue = taskQueue;
				this.treeView = treeView;
				this.nodeIter = nodeIter;

				prevProgress = 0;
			}

			public void OnUpdated(object o, ProgressSegment.UpdatedEventArgs args)
			{
				float diff = args.LocalProgress - prevProgress;
				if (diff > .001f)
				{
					float newProg = args.LocalProgress;
					taskQueue.Add(
						delegate()
						{
							treeView.SetNodeProgress(nodeIter, newProg);
						});
					prevProgress = args.LocalProgress;
				}
			}

			public void Complete()
			{
				taskQueue.Add(
					delegate()
					{
						treeView.SetNodeProgress(nodeIter, 1);
					});
			}
		}
	}
}
